﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management.Automation;
using System.Threading;
using Microsoft.Hpc.Scheduler;

namespace pshcli
{
    [Cmdlet("Run", "Dwarf2")]
    public class PSH_RunDwarf2 : Cmdlet
    {
        [Parameter(
            Position = 0,
            ValueFromPipeline = true,
            Mandatory = false,
            HelpMessage = "Dwarf object to be executed")]
        public Dwarf Dwarf
        {
            get { return dwarf; }
            set { dwarf = value; }
        }


        Dwarf dwarf;
        List<Dwarf> dwarflist;
        ProgressRecord all;
        bool terminate = false;
        bool xperfon;




        protected override void BeginProcessing()
        {
            dproc = null;
            dwarflist = new List<Dwarf>();
            all = new ProgressRecord(1, "Total dwarf execution", "Progress...");
            all.PercentComplete = 0;
            terminate = false;
            xperfon = false;

            Console.TreatControlCAsInput = false;
            Console.CancelKeyPress += ConsoleEventHandler;
        }

        protected override void ProcessRecord()
        {
            dwarflist.Add((Dwarf)dwarf);
        }




        public static string cscheduler;
        private static Microsoft.Hpc.Scheduler.Scheduler ClusterSchedulerObject = null;
        //private static int jobid = -1;
        public static ManualResetEvent jobFinishEvt = new ManualResetEvent(false);
        public static bool successfuljob = false;
        public static bool waitfornext = true;
        private static System.Diagnostics.Process dproc;       



        protected override void EndProcessing()
        {
            int failed = 0;
            double totalTime = 0;
        

            int dall = (int)(100 / dwarflist.ToArray().Length);
            this.WriteProgress(all);

            this.WriteVerbose("Run-Dwarf2: Starting execution.");

            foreach(Dwarf _dwarf in dwarflist)
            {
                if (!terminate)
                {
                    if (_dwarf.CeckConsistency())
                    {
                        Console.Write(_dwarf.dwarfname + "." + _dwarf.platform + "." + _dwarf.parallel + "." + _dwarf.size + " started...");
                        string execmd = _dwarf.ConstructExecCommand();

                        totalTime = ExecuteDwarf(_dwarf);
                    }
                    else _dwarf.success = false;
                }
                else
                {
                    _dwarf.success = false;
                    _dwarf.errormsg = "Execution skipped because of Run-Dwarf termination";
                }

                if (_dwarf.success)
                {
                    _dwarf.GetResult();
                    Console.WriteLine(" done. Kernel time: " + _dwarf.time + " seconds");
                    Console.WriteLine("Total processor time: " + totalTime + " seconds");
                }
                
                all.PercentComplete += dall;
                this.WriteProgress(all);
                if (_dwarf.success == false)
                    failed++;

                this.WriteObject(_dwarf);
            }

            this.WriteVerbose("Run-Dwarf2: Starting execution.");

            all.PercentComplete = 100;
            this.WriteProgress(all);
            Console.CancelKeyPress -= ConsoleEventHandler;
            terminate = false;
         
        }


        private double ExecuteDwarf(Dwarf d)
        {
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            dproc = new System.Diagnostics.Process();

            if ((d.parallel == parallel.mpi ||
                d.parallel == parallel.hybrid ||
                d.parallel == parallel.mpinet) && 
                (!String.IsNullOrEmpty(d.scheduler)))
            { 
                this.WriteVerbose("Launching scheduler execution.");


                ClusterSchedulerObject = new Scheduler();
                ClusterSchedulerObject.Connect(d.scheduler);

                ISchedulerJob job = ClusterSchedulerObject.CreateJob();
                job.Name = dwarf.dwarfname;
                job.Project = "Dwarfs";

                ISchedulerTask task = job.CreateTask();
                task.MinimumNumberOfCores = dwarf.numprocs;
                task.MaximumNumberOfCores = dwarf.numprocs;
                task.Name = dwarf.dwarfname + "(" + dwarf.platform + "," + dwarf.parallel + "," + dwarf.size + ")";
                task.IsExclusive = true;
                task.WorkDirectory = dwarf.workdir;
                task.CommandLine = dwarf.ConstructExecCommand();

                job.AddTask(task);

                job.OnJobState += new EventHandler<JobStateEventArg>(job_OnJobState);

              
                watch.Start();
                ClusterSchedulerObject.SubmitJob(job, null, null);

                //wait for job to complete...
                while (waitfornext)
                {
                    jobFinishEvt.WaitOne();
                }
                watch.Stop();

                this.WriteVerbose("Scheduler execution complete.");    
      
                //reset values back for next mpi_run
                waitfornext = true;
            }
            else //Execute locally
            {
                if (d.parallel == parallel.hybrid ||
                    d.parallel == parallel.mpi ||
                    d.parallel == parallel.mpinet)
                {
                    string mpiexecpath = System.Environment.GetEnvironmentVariable("CCP_SDK");
                    dproc.StartInfo.FileName = mpiexecpath + "Bin\\mpiexec.exe";
                }
                else
                    dproc.StartInfo.FileName = d.bin;
                dproc.StartInfo.WorkingDirectory = d.workdir;
                dproc.StartInfo.Arguments = d.ConstructExecCommand();
                dproc.StartInfo.UseShellExecute = false;
                dproc.StartInfo.CreateNoWindow = true;

                this.WriteVerbose("dproc startinfo filename: " + dproc.StartInfo.FileName);
                this.WriteVerbose("dproc startinfo args: " + dproc.StartInfo.Arguments);
                this.WriteVerbose("dproc wdir: " + dproc.StartInfo.FileName);

                if (d.parallel == parallel.omp)
                {
                    dproc.StartInfo.EnvironmentVariables.Add("OMP_NUM_THREADS", d.numthreads.ToString());
                }

                if (d.xperftrace && String.IsNullOrEmpty(d.scheduler))
                {
                    this.WriteVerbose("RUN-DWARF: Xperf active with trace file: " + d.xperftracefile);
                    xperfon = true;
                    HelperMethods.StartXperf(d.xperftracefile);
                }
                else //this should be in get-dwarf
                    d.xperftrace = false;

                watch.Start();
                dproc.Start();
                this.WriteVerbose("Launching local process. (Id: " + dproc.Id + ")");
                dproc.WaitForExit();
                watch.Stop();

                if (d.xperftrace && String.IsNullOrEmpty(d.scheduler))
                {
                    HelperMethods.StopXperf();
                    xperfon = false;
                }

                this.WriteVerbose("Process exited.");

                dproc.Refresh();
                int exitcode = dproc.ExitCode;

                if (exitcode < 0)
                {
                    Console.WriteLine("\nProcess {0} ended with exit code {1}.", dproc.Id, dproc.ExitCode);
                    d.success = false;
                }
                else
                    d.success = true;
            }

            return watch.ElapsedMilliseconds / (double)1000;
        }


        static void job_OnJobState(object sender, JobStateEventArg e)
        {
            Console.WriteLine("Job (ID:{0}) state: {1}", e.JobId, e.NewState);

            if (e.NewState.Equals(Microsoft.Hpc.Scheduler.Properties.JobState.Finished))
            {
                successfuljob = true;
                waitfornext = false;
                jobFinishEvt.Set();
                return;
            }
            if (e.NewState.Equals(Microsoft.Hpc.Scheduler.Properties.JobState.Failed) ||
                e.NewState.Equals(Microsoft.Hpc.Scheduler.Properties.JobState.Canceled))
            {
                Console.WriteLine("Job ended with status: " + e.NewState);
                successfuljob = false;
                waitfornext = false;
                jobFinishEvt.Set();
                return;
            }
        }


        void ConsoleEventHandler(object o, ConsoleCancelEventArgs a)
        {
            if (String.IsNullOrEmpty(cscheduler))
            {
                //Console.WriteLine("Ctrl-C pressed.");
                if (dproc != null)
                {
                    if (!dproc.HasExited)
                    {
                        try
                        {
                            Console.Write("\nCtrl-C closing process (Id " + dproc.Id + ")...");
                            dproc.Kill();
                            dproc.WaitForExit();
                            Console.WriteLine(" closed.");

                            if (xperfon)
                                HelperMethods.StopXperf();
                        }
                        catch (Exception e)
                        {
                            if (!e.Message.ToString().Contains(" has exited."))
                                Console.WriteLine("KILL EXCEPTION : " + e.ToString());
                        }
                    }
                    terminate = true;
                }
            }
            else
            {
                //cscheduler.CancelJob();
                terminate = true;
            }
        }

    }
}
